home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Meeting Pearls 4
/
Meeting Pearls Vol. IV (1996)(GTI - Schatztruhe)[!].iso
/
Pearls
/
disk
/
scsi
/
AGMSSetSCSI
/
AGMSSetSCSI.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-09-26
|
28KB
|
899 lines
/******************************************************************************
* AGMSSetSCSI - A utility for setting SCSI mode page device parameters.
*
* $Header: Big:Programming/C/AGMSSetSCSI/RCS/AGMSSetSCSI.c,v 1.5 1996/09/26 20:27:32 AGMS Exp $
*
* Implemented by Alexander G. M. Smith, Ottawa Canada, agmsmith@achilles.net,
* agmsmith@bix.com, 71330.3173@compuserve.com, and various other places
* including the Ottawa Freenet.
*
* This code is put into the public domain by AGMS, so you can copy it,
* hack it up, sell it, and do whatever you want to it.
*
* Compile with the GNU C compiler, gcc version 2.7.0, use the command line:
* gcc -v -noixemul -O2 -Wall AGMSSetSCSI.c
*
* Or compile with the SAS C compiler, version 6.56. I use 16 bit integers,
* but that shouldn't matter (the GNU version uses 32).
*
* This program changes various parameters that SCSI devices have. You should
* first get the device's manual to find out what the actual bits do. It is
* very easy to make a mistake and wipe out your hard drive, so don't try this
* unless you know what you are doing.
*
* I wrote this in an attempt to turn off plug-and-play SCSI in a Quantum
* Fireball hard drive (thinking it was causing problems with the SCSI
* controller on my Apollo 2030 68030/25 CPU board), as well as to set hard
* drive spin-down timeout values and other goodies (fortunately Quantum has
* a web site (http://www.quantum.com/products/manuals/) that describe all
* the mode page parameters).
*
* $Log: AGMSSetSCSI.c,v $
* Revision 1.5 1996/09/26 20:27:32 AGMS
* Final bit of polishing done.
*
* Revision 1.4 1996/09/24 23:32:28 AGMS
* First working version.
*
* Revision 1.3 1996/09/23 15:40:43 AGMS
* Now does a mode sense command and parses the returned data.
*
* Revision 1.2 1996/09/15 15:35:55 AGMS
* Command line parsing finished. Now works with SAS C.
*
* Revision 1.1 1996/09/08 17:24:08 AGMS
* Initial revision
*/
#ifdef __SASC
#define __USE_SYSBASE 1
/* Need this to make the exec.library headers use the library base global
variable SysBase in SAS C (otherwise it just uses location $4). */
#endif
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/utility.h>
#ifndef EXEC_EXECBASE_H
#include <exec/execbase.h>
#endif
#ifndef EXEC_MEMORY_H
#include <exec/memory.h>
#endif
#ifndef WORKBENCH_STARTUP_H
#include <workbench/startup.h>
#endif
#ifndef DEVICES_SCSIDISK_H
#include <devices/scsidisk.h>
#endif
#ifdef _GST
extern struct ExecBase *SysBase;
/* If using precompiled headers (GST - global symbol table) in SAS C, the
extern declaration in the headers doesn't take effect. */
#endif /* _GST */
#ifdef __GNUC__
/******************************************************************************
* Stuff imported from the Libnix runtime library for use with GNU C.
*/
extern struct WBStartup *_WBenchMsg;
/* The workbench startup message is put here by the runtime if started from
the workbench, NULL when started from the command line. */
const char __nocommandline;
/* Create this variable to make libnix turn off command line parsing. */
#endif /* __GNUC__ */
#ifndef __AMIGADATE__
/* This doesn't look as good as the real __AMIGADATE__ format, but it's
close enough for me, if not the system's VERSION command. */
#define __AMIGADATE__ "(" __DATE__ ")"
#endif
/******************************************************************************
* Command line argument parsing stuff.
*/
static const char RDArgsTemplate [] =
"DEVICE/K,UNIT/A/N,PAGE/A,CURRENT/S,MODIFIABLE/S,DEFAULT/S,SAVED/S,"
"WRITECURRENT/S,WRITESAVED/S,CHANGES/M";
/* Template for parsing the command line, native Amiga style. */
enum RDArgsEnum {
DEVICE_RDARGINDEX = 0,
UNIT_RDARGINDEX,
PAGE_RDARGINDEX,
CURRENT_RDARGINDEX,
MODIFIABLE_RDARGINDEX,
DEFAULT_RDARGINDEX,
SAVED_RDARGINDEX,
WRITECURRENT_RDARGINDEX,
WRITESAVED_RDARGINDEX,
CHANGES_RDARGINDEX,
NUMRDARGS
};
/* Argument numbers corresponding to the command line parsing template. */
#define MAXDEVICENAME 80
char DeviceName [MAXDEVICENAME];
/* Name of the device driver to use, usually scsi.device. */
LONG UnitNumber;
/* SCSI disk unit to use, xyz decimal format, x is board number for multiple
SCSI controller boards (usually 0), y is LUN of subdevice (usually 0), z is
SCSI ID of device. */
int LUN;
/* Logical unit number derived from UnitNumber, 0 to 9. */
UBYTE PageNumber;
/* Which SCSI parameters mode page to look at and maybe change. */
enum DataSourceEnum {
CURRENT = 0,
MODIFIABLE,
DEFAULT,
SAVED,
MAXDATASOURCES
} DataSource;
/* Which kind of data to read and write from the device. */
char *DataSourceNames [MAXDATASOURCES] =
{
"current",
"modifiable",
"default",
"saved"
};
BOOL WriteCurrent;
/* TRUE if the user wants to actually write the changes to the current SCSI
settings page. */
BOOL WriteSaved;
/* TRUE if the user wants to actually write the changes to the saved /
permanent SCSI settings page. Actually, all current pages get copied to the
saved pages. */
struct ChangeStruct {
UBYTE offset;
UBYTE value;
} ChangeArray [256];
int NumberOfChanges;
/* The user's list of changes expressed in offsets and values. */
const char HelpText [] =
"AGMSSetSCSI is a utility for setting SCSI mode page device parameters.\n"
"Since this can destroy your SCSI device, you should only use it when\n"
"you know what you are doing!!!\n"
"\n"
"Public domain by Alexander G. M. Smith, Ottawa Canada, send questions to\n"
"agmsmith@achilles.net, agmsmith@bix.com, 71330.3173@compuserve.com.\n"
"$VER: AGMSSetSCSI 1.0 " __AMIGADATE__ " ($Id: AGMSSetSCSI.c,v 1.5 1996/09/26 20:27:32 AGMS Exp $)\n"
"\n"
"DEVICE is the name of the SCSI device driver, it defaults to a device\n"
"with SCSI in its name, or scsi.device if it can't find one. You need\n"
"to type the keyword DEVICE if you want to specify it.\n"
"\n"
"UNIT is the decimal SCSI unit number, use tens for LUN and hundreds for\n"
"controller board number. So 456 would be board 4 (5th board), LUN\n"
"(logical unit number, sub-unit of the device) number 5, at SCSI ID\n"
"number 6. You need to specify a value for this one.\n"
"\n"
"PAGE is the mode page number, in hexadecimal. Each page covers some\n"
"function, such as control of caching, control of idle time power down,\n"
"etc. You need to specify this one. See your drive manual for details\n"
"and other sources for the standard pages (like the ProbeSCSI program by\n"
"Ron Klinkien or http://www.quantum.com/products/manuals/).\n"
"\n"
"CURRENT, SAVED, DEFAULT, MODIFIABLE specify the source of the data.\n"
"Current is for the current settings (they are copied from the SAVED\n"
"values when your SCSI device starts up), SAVED is for the permanent\n"
"settings saved in the SCSI device, and DEFAULT is for the factory\n"
"settings. MODIFIABLE is a special data source that has 1 bits in\n"
"positions which are modifiable. Defaults to CURRENT.\n"
"\n"
"WRITECURRENT will make the program write your changes back to the SCSI\n"
"device's current settings, into the same page where they came from.\n"
"WRITESAVED writes all current pages to the permanent saved settings,\n"
"not just the one you were editing. Defaults to not writing back the\n"
"values. Only specify one of these two.\n"
"\n"
"CHANGES are a list of offset and value pairs. Both are in hex and are\n"
"separated by a slash. For example \"4/1a c/ff\" would set the byte at\n"
"offset $4 (4 decimal) in the page to $1A (26) and the byte at\n"
"offset $C (12) to $FF (255).\n"
"\n";
/******************************************************************************
* Things too large to be on the stack or that are global.
*/
char ErrorMessage [256];
/* For formatting error messages into. */
UBYTE ModePageData [256];
/* The currently read in mode page. */
int ModePageDataLength;
/* Number of bytes of data in the ModePageData array. */
UBYTE MediumType;
/* Saved data from the Mode Sense that needs to be regurgitated
for the Mode Select command. */
UBYTE ChangedModePageData [256];
/* The new mode page data, after the user's changes. */
/******************************************************************************
* Data structures needed for talking to scsi.device.
*/
struct MsgPort *MyMessagePort;
/* For receiving replies to device requests. */
struct IOStdReq *MyIORequest;
/* Commands to scsi.device are put into this IO request record. */
struct SCSICmd *MySCSICommand;
/* The IO request points to this record which points to the various buffers
needed for running the command. If the buffer pointer fields in this record
are NULL then the buffers haven't been allocated yet. */
#define SCSI_BUFFER_SIZES 256
/* All the buffers (data, command, sense) in MySCSICommand are this size and
are allocated from DMA-able memory. Though scsi.device is reported to use
programmed IO rather than DMA for transfers <256 bytes. */
BOOL DeviceWasOpened;
/* TRUE if the scsi.device was OpenDevice'd and needs to be closed when the
program exits. */
struct SCSIModeSenseStruct
{
UBYTE commandCode;
/* SCSI command opcode: high 3 bits are command group, low 5 are command,
group 0 is 6 bytes, group 1 & 2 are 10 bytes). Mode sense command is $1a,
Mode select is $15. The rest of this structure is set up for group 0
mode sense commands since most devices should support them. The mode
sense command reads one or more pages of SCSI parameter data. */
UBYTE lun;
/* Logical unit number, for subdevices off a main one, in high 3 bits.
Usually 0, sometimes used for jukebox devices. Bit $08 can be set to turn
off sending of block descriptors in the sense data. */
UBYTE pageCode;
/* Which page of data to read for MODE SENSE. The high 2 bits specify the
data source, 00=current data, 01=fake page with all modifiable bits set to
one, 10=default values, 11=saved values. Low 6 bits are page number. */
UBYTE reserved1;
/* Set this to zero for MODE SENSE. */
UBYTE allocationLength;
/* The number of bytes of returned sense data we can accept. We will use
$FE since our returned sense data buffer is at least that big and the
number has to be even for scsi.device. */
UBYTE reserved2;
/* Also set this to zero. Bits used for linked commands. */
};
struct SCSIModeSelectStruct
{
UBYTE commandCode;
/* SCSI command opcode. Mode select command is $15. This command writes
to a page of SCSI parameter settings. */
UBYTE lun;
/* Logical unit number, for subdevices off a main one, in high 3 bits.
There are some other control bits, but leave them zero. Least significant
bit (value $01) saves the parameters permanently (all the current settings
pages are saved, not just the page being changed). */
UBYTE reserved1;
/* Set this to zero for MODE SELECT. */
UBYTE reserved2;
/* Set this to zero for MODE SELECT. */
UBYTE parameterLength;
/* The number of bytes of outgoing sense data, including headers etc.
Actual amount depends on the page being set. 4 byte header (we don't
send the block descriptor), plus a 2 byte page header (we just send
one page), plus page data (same size as was in mode sense page data). */
UBYTE reserved3;
/* Also set this to zero. Bits used for linked commands. */
};
/******************************************************************************
* Look at the argument values the user specified and try to make sense of
* them. Also generates defaults for unspecified settings.
*/
static BOOL ExamineArguments (LONG ArgumentValues [NUMRDARGS])
{
char *ChangeStart;
char *ChangeString;
struct Node *CurrentNode;
BOOL Success;
char *FromChar;
int i;
char *ParseString;
char **StringPntrPntr;
char *ToChar;
/* Get the device name. Search device list for something with SCSI
in it if the user didn't specify a value. */
if (ArgumentValues [DEVICE_RDARGINDEX] == 0)
{
CurrentNode = SysBase->DeviceList.lh_Head;
Success = FALSE;
while (CurrentNode->ln_Succ != NULL)
{
/* Copy the potential device name into our device name buffer and
convert it to upper case for comparison purposes. Also truncates
names that are too long. */
for (FromChar = CurrentNode->ln_Name, ToChar = DeviceName, i = 0;
*FromChar && i < MAXDEVICENAME - 1;
++FromChar, ++ToChar, ++i)
*ToChar = toupper (*FromChar);
*ToChar = 0;
if (strstr (DeviceName, "SCSI"))
{
strncpy (DeviceName, CurrentNode->ln_Name, MAXDEVICENAME);
Success = TRUE;
break;
}
CurrentNode = CurrentNode->ln_Succ;
}
if (!Success)
strcpy (DeviceName, "scsi.device");
}
else
strncpy (DeviceName, (char *) (ArgumentValues [DEVICE_RDARGINDEX]),
MAXDEVICENAME);
DeviceName [MAXDEVICENAME-1] = 0; /* In case strncpy failed. */
/* Read the unit number. The reference is a pointer to the long number. */
if (ArgumentValues [UNIT_RDARGINDEX] != 0)
{
UnitNumber = *((LONG *) (ArgumentValues [UNIT_RDARGINDEX]));
LUN = (UnitNumber / 10) % 10;
}
/* And now the page number. */
if (ArgumentValues [PAGE_RDARGINDEX] != 0)
{
PageNumber = strtol ((char *) (ArgumentValues [PAGE_RDARGINDEX]),
NULL /* No output endpointer */, 16 /* Base for conversion */);
PageNumber &= 0x3F; /* Values are from 0 to $3F. */
}
/* Find the data source. */
for (i = CURRENT_RDARGINDEX; i <= SAVED_RDARGINDEX; ++i)
{
if (ArgumentValues [i])
DataSource = i - CURRENT_RDARGINDEX;
}
/* Does the user wants to actually write the changes? */
if (ArgumentValues [WRITECURRENT_RDARGINDEX])
WriteCurrent = TRUE;
if (ArgumentValues [WRITESAVED_RDARGINDEX])
WriteSaved = TRUE;
/* Collect the user's list of changes and convert to binary. */
if (ArgumentValues [CHANGES_RDARGINDEX] != 0)
{
StringPntrPntr = (char **) (ArgumentValues [CHANGES_RDARGINDEX]);
while ((ChangeString = *StringPntrPntr++) != NULL && NumberOfChanges < 256)
{
Success = FALSE;
ChangeArray [NumberOfChanges] . offset =
strtol (ChangeString, &ParseString, 16);
if (ParseString != NULL && *ParseString /* there's more */ &&
ParseString != ChangeString /* some characters were used */)
{
++ParseString; /* Skip over the offset/value separator. */
ChangeStart = ParseString;
ChangeArray [NumberOfChanges] . value =
strtol (ParseString, &ParseString, 16);
if (ParseString != ChangeStart /* some was used */)
{
++NumberOfChanges;
Success = TRUE;
}
}
if (!Success)
printf ("Change \"%s\" ignored (want hex number, \"/\", hex #).\n",
ChangeString);
}
}
printf ("Using %s unit %ld (ID %d, LUN %d), page $%02x, \"%s\" data source.\n",
DeviceName, UnitNumber, (int) (UnitNumber % 10), (int) LUN, (int) PageNumber,
DataSourceNames [DataSource]);
return TRUE;
}
/******************************************************************************
* Read the command line arguments and set up various things. Returns TRUE
* if successful.
*/
static BOOL ParseArguments (void)
{
LONG ArgumentValues [NUMRDARGS];
struct TagItem DoneTag = {TAG_DONE, 0};
LONG ErrorCode;
struct RDArgs *RDArgParametersPntr;
BOOL ReturnCode;
if (_WBenchMsg != NULL)
return FALSE; /* If started from the workbench, no command line exists. */
RDArgParametersPntr = AllocDosObject (DOS_RDARGS, &DoneTag);
if (RDArgParametersPntr == NULL)
{
printf ("Out of memory for allocating RDARGS structure.\n");
return FALSE;
}
RDArgParametersPntr->RDA_ExtHelp = (char *) HelpText;
memset (ArgumentValues, 0, sizeof (ArgumentValues));
ReturnCode = (NULL != ReadArgs ((STRPTR) RDArgsTemplate,
ArgumentValues, RDArgParametersPntr));
if (ReturnCode)
ReturnCode = ExamineArguments (ArgumentValues);
else
{
ErrorCode = IoErr ();
strcpy (ErrorMessage, ": ?");
Fault (ErrorCode, (char *) "", ErrorMessage, sizeof (ErrorMessage));
printf ("Oops%s, please use \"AGMSSetSCSI ?\" for\n"
"help and type a second \"?\" to get even more help.\n", ErrorMessage);
}
FreeArgs (RDArgParametersPntr);
FreeDosObject (DOS_RDARGS, RDArgParametersPntr);
return ReturnCode;
}
/******************************************************************************
* Opens the scsi.device driver and sets up the related data structures.
* Returns TRUE if successful.
*/
BOOL OpenSCSIDevice (void)
{
int ErrorCode;
MyMessagePort = CreateMsgPort ();
if (MyMessagePort == NULL)
{
printf ("Out of memory for CreateMsgPort.\n");
return FALSE;
}
MyIORequest = CreateIORequest (MyMessagePort, sizeof (struct IOStdReq));
if (MyIORequest == NULL)
{
printf ("Out of memory for CreateIORequest.\n");
return FALSE;
}
ErrorCode = OpenDevice (DeviceName, UnitNumber,
(struct IORequest *) MyIORequest, 0L);
if (ErrorCode != 0)
{
printf ("OpenDevice for %s unit %d failed, code %d.\n",
DeviceName, (int) UnitNumber, ErrorCode);
return FALSE;
}
MySCSICommand = AllocMem (sizeof (struct SCSICmd), MEMF_PUBLIC | MEMF_CLEAR);
if (MySCSICommand == NULL)
{
printf ("Out of memory for SCSICmd record.\n");
return FALSE;
}
/* Set up for a direct SCSI command through the scsi.device. */
MyIORequest->io_Command = HD_SCSICMD;
MyIORequest->io_Data = MySCSICommand;
MyIORequest->io_Length = sizeof (struct SCSICmd);
MySCSICommand->scsi_Data =
AllocMem (SCSI_BUFFER_SIZES, MEMF_24BITDMA | MEMF_PUBLIC);
MySCSICommand->scsi_Command =
AllocMem (SCSI_BUFFER_SIZES, MEMF_24BITDMA | MEMF_PUBLIC);
MySCSICommand->scsi_SenseData =
AllocMem (SCSI_BUFFER_SIZES, MEMF_24BITDMA | MEMF_PUBLIC);
if (MySCSICommand->scsi_Data == NULL ||
MySCSICommand->scsi_Command == NULL ||
MySCSICommand->scsi_SenseData == NULL)
{
printf ("Ran out of 24 bit DMA accessible memory for data buffers.\n");
return FALSE;
}
return TRUE;
}
/******************************************************************************
* Submits the SCSI command in MyIORequest/MySCSICommand to the scsi.device
* and prints out any resulting errors. Returns TRUE if no errors.
*/
BOOL SubmitSCSICommand (void)
{
int ErrorCode;
int i;
MySCSICommand->scsi_Status = 0; /* In case a *.device IO error happens. */
MySCSICommand->scsi_SenseActual = 0; /* Some scsi drivers don't set this. */
ErrorCode = DoIO ((struct IORequest *) MyIORequest);
if (ErrorCode != 0)
printf ("DoIO failed for the SCSI command, code %d.\n", ErrorCode);
if (MySCSICommand->scsi_Status != 0)
{
printf ("SCSI command failed, SCSI error code %d.",
(int) MySCSICommand->scsi_Status);
if (MySCSICommand->scsi_SenseActual > 0)
{
printf (" Associated sense data:\n");
for (i = 0; i < MySCSICommand->scsi_SenseActual; i++)
printf ("$%02x ", (int) MySCSICommand->scsi_SenseData[i]);
}
printf ("\n");
return FALSE;
}
return (ErrorCode == 0);
}
/******************************************************************************
* Load in the mode sense values from the page the user asked for and display
* them on stdout as well as storing them in ModePageData. Returns TRUE if
* successful, FALSE if the device doesn't have that page or some other
* error happens.
*/
BOOL ReadSCSISettings (void)
{
BOOL ChangeableBit;
BOOL FoundOurPage;
int i;
int Index;
int MaxIndex;
struct SCSIModeSenseStruct *ModeSenseCommand;
UBYTE *ModeSenseData;
int PageID;
int PageLength;
ModeSenseCommand = (void *) MySCSICommand->scsi_Command;
ModeSenseCommand->commandCode = 0x1A; /* Mode sense command. */
ModeSenseCommand->lun = (LUN << 5);
ModeSenseCommand->pageCode = (DataSource << 6) | PageNumber;
ModeSenseCommand->reserved1 = 0;
ModeSenseCommand->allocationLength = 254; /* Must be even. */
ModeSenseCommand->reserved2 = 0;
MySCSICommand->scsi_Length = 254; /* Can take this much answer data. */
MySCSICommand->scsi_CmdLength = sizeof (struct SCSIModeSenseStruct);
MySCSICommand->scsi_SenseLength = 254; /* Error message buffer size. */
MySCSICommand->scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE;
if (!SubmitSCSICommand ())
return FALSE;
/* Analyse the data received from the mode sense. */
ModeSenseData = (void *) MySCSICommand->scsi_Data;
if (MySCSICommand->scsi_Actual == 0)
{
printf ("No data received at all from the mode sense command!\n");
return FALSE;
}
printf ("Received %ld mode sense bytes. Raw mode sense data in hexadecimal:\n",
(long) MySCSICommand->scsi_Actual);
for (Index = 0; Index < (int) MySCSICommand->scsi_Actual; Index++)
{
printf (" %02x", (int) ModeSenseData [Index]);
if (Index % 16 == 15)
printf ("\n");
}
if (Index % 16 != 0)
printf ("\n");
MaxIndex = ModeSenseData [0]; /* First byte is count of remainder. */
if (MaxIndex >= MySCSICommand->scsi_Actual)
{
printf ("Header says there is more mode sense data (%d) than we\n"
"received (%d). Using only what we recevied.\n", MaxIndex,
(int) MySCSICommand->scsi_Actual);
MaxIndex = MySCSICommand->scsi_Actual;
}
else
MaxIndex++; /* Length byte not included in length. */
/* MaxIndex is one past last valid data array index. */
Index = 3; /* Index of size of descriptor blocks, in standard header. */
if (Index >= MaxIndex)
{
printf ("Mode sense data too short for even standard header!\n");
return FALSE;
}
MediumType = ModeSenseData [1]; /* Save this field for later use. */
Index = ModeSenseData [Index] + 4; /* Skip past block descriptors. */
FoundOurPage = FALSE;
while (Index < MaxIndex)
{
PageID = ModeSenseData [Index];
PageID &= 0x3F; /* Only lower 6 bits are for the page number. */
ChangeableBit = (ModeSenseData [Index] & 0x80) != 0;
PageLength = ModeSenseData [Index+1];
printf ("Found page $%02x, %s, containing %d bytes:\n",
PageID, ChangeableBit ? "changeable" : "read only", PageLength);
Index += 2;
if (Index + PageLength > MaxIndex)
{
printf ("Oops, that page would run past the end of the received data!\n");
return FALSE;
}
printf ("\t");
for (i = 0; i < PageLength; i++)
printf (" %02x", (int) i);
printf ("\n\t");
for (i = 0; i < PageLength; i++)
printf (" %02x", (int) ModeSenseData [Index + i]);
printf ("\n");
if (PageLength == 0)
{
printf ("Wait a sec, page length zero? "
"Is this an old NEC CDROM? Bye-bye!\n");
return FALSE;
}
if (PageID == PageNumber)
{
/* Found the page the user is looking for, copy the data to
our working array. */
FoundOurPage = TRUE;
ModePageDataLength = PageLength;
for (i = 0; i < PageLength; i++)
ModePageData [i] = ModeSenseData [Index + i];
}
Index += PageLength; /* Advance to the next page. */
}
if (FoundOurPage)
return TRUE;
printf ("The SCSI device didn't provide your requested mode sense page!\n");
return FALSE;
}
/******************************************************************************
* Write back the changed settings to the given mode page and data
* destination. Returns TRUE if successful (no errors, including not
* writing case).
*/
BOOL WriteSCSISettings (void)
{
enum DataSourceEnum DataDestination;
int i;
struct SCSIModeSelectStruct *ModeSelectCommand;
UBYTE *ModeSelectData;
/* Apply the changes to the data. */
for (i = 0; i < ModePageDataLength; i++)
ChangedModePageData [i] = ModePageData [i];
for (i = 0; i < NumberOfChanges; i++)
ChangedModePageData [ChangeArray [i] . offset] =
ChangeArray [i] . value;
/* Print the changed numbers in highlighted mode. */
printf ("Now have %d bytes of changed data for page $%02x:\n\t",
ModePageDataLength, (int) PageNumber);
for (i = 0; i < ModePageDataLength; i++)
printf ((ChangedModePageData [i] == ModePageData [i]) ?
" %02x" : " \033[2m%02x\033[m", (int) ChangedModePageData [i]);
printf ("\n");
/* Decide where to write the data, if anywhere. */
if (WriteSaved)
DataDestination = SAVED;
else if (WriteCurrent)
DataDestination = CURRENT;
else
{
printf ("Mode page data not written back, as you requested.\n");
return TRUE;
}
printf ("Writing to %s data for page $%02x...\n",
DataSourceNames [DataDestination], (int) PageNumber);
if (DataDestination == SAVED)
printf ("Note that writing to saved data writes ALL the current pages,\n"
"not just the one you are editing. So, if you changed other\n"
"current pages, those changes will be saved too.\n");
ModeSelectCommand = (void *) MySCSICommand->scsi_Command;
ModeSelectCommand->commandCode = 0x15; /* Mode select command. */
ModeSelectCommand->lun = (LUN << 5);
if (DataDestination == SAVED)
ModeSelectCommand->lun |= 1; /* Set low bit to save permanently. */
ModeSelectCommand->reserved1 = 0;
ModeSelectCommand->reserved2 = 0;
ModeSelectCommand->parameterLength = 4 + 2 + ModePageDataLength;
ModeSelectCommand->reserved3 = 0;
MySCSICommand->scsi_Length = ModeSelectCommand->parameterLength;
MySCSICommand->scsi_CmdLength = sizeof (struct SCSIModeSelectStruct);
MySCSICommand->scsi_SenseLength = 254; /* Error message buffer size. */
MySCSICommand->scsi_Flags = SCSIF_WRITE | SCSIF_AUTOSENSE;
/* Parameter list data for the mode select command to use. First comes the
4 byte parameter list header. */
ModeSelectData = (void *) MySCSICommand->scsi_Data;
ModeSelectData [0] = 0; /* Reserved zero. */
ModeSelectData [1] = MediumType; /* Regurgitating. */
ModeSelectData [2] = 0; /* Reserved zero. */
ModeSelectData [3] = 0; /* Block descriptor length, we don't have one. */
/* No block descriptors, so we move right into a mode page header. */
ModeSelectData [4] = PageNumber;
ModeSelectData [5] = ModePageDataLength;
/* And finally the actual data. */
for (i = 0; i < ModePageDataLength; i++)
ModeSelectData [6 + i] = ChangedModePageData [i];
if (!SubmitSCSICommand ())
return FALSE;
printf ("Write successfully completed.\n");
return TRUE;
}
/******************************************************************************
* Called at program exit to clean up globally allocated things.
*/
void CleanUp (void)
{
if (DeviceWasOpened)
{
CloseDevice ((struct IORequest *) MyIORequest);
DeviceWasOpened = FALSE;
}
if (MySCSICommand != NULL)
{
if (MySCSICommand->scsi_Data != NULL)
FreeMem (MySCSICommand->scsi_Data, SCSI_BUFFER_SIZES);
if (MySCSICommand->scsi_Command != NULL)
FreeMem (MySCSICommand->scsi_Command, SCSI_BUFFER_SIZES);
if (MySCSICommand->scsi_SenseData != NULL)
FreeMem (MySCSICommand->scsi_SenseData, SCSI_BUFFER_SIZES);
FreeMem (MySCSICommand, sizeof (struct SCSICmd));
MySCSICommand = NULL;
}
if (MyIORequest != NULL)
{
DeleteIORequest (MyIORequest);
MyIORequest = NULL;
}
if (MyMessagePort != NULL)
{
DeleteMsgPort (MyMessagePort);
MyMessagePort = NULL;
}
}
/******************************************************************************
* The usual main program entry point.
*/
int main (void)
{
atexit (CleanUp);
if (!ParseArguments ())
return 10; /* Failure somewhere, or run from Workbench, not shell. */
if (!OpenSCSIDevice ())
return 20; /* Failed to open and allocate things. */
if (!ReadSCSISettings ())
return 21; /* Failed to access the SCSI device's settings. */
if (!WriteSCSISettings ())
return 22; /* Something went wrong while setting the SCSI attributes. */
return 0; /* Success! */
}